From 96e049d5e88703cb694dfeb253ec6fa8452157a1 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:27:57 -0600 Subject: [PATCH] Generalize xmlgeneric callbacks (#532) generalize callbacks for xmlgeneric. use newly available pointer to data member callbacks to restore yahoo functionality. add a yahoo test. . add xml_init method to automatically generate Functors. --- reference/yahoo.csv | 2 + reference/yahoo.xml | 17 +++++++ testo.d/yahoo.test | 4 ++ xmlgeneric.cc | 60 +++++++++++++++++++----- xmlgeneric.h | 109 ++++++++++++++++++++++++++++++++++++++++++-- yahoo.cc | 42 ++--------------- yahoo.h | 62 +++++++++++++++---------- 7 files changed, 219 insertions(+), 77 deletions(-) create mode 100644 reference/yahoo.csv create mode 100644 reference/yahoo.xml create mode 100644 testo.d/yahoo.test diff --git a/reference/yahoo.csv b/reference/yahoo.csv new file mode 100644 index 000000000..a8f590e02 --- /dev/null +++ b/reference/yahoo.csv @@ -0,0 +1,2 @@ +No,Latitude,Longitude,Name +1,37.416384,-122.024853,"701 FIRST AVE, SUNNYVALE, CA, 94089-1019, US" diff --git a/reference/yahoo.xml b/reference/yahoo.xml new file mode 100644 index 000000000..7425755a5 --- /dev/null +++ b/reference/yahoo.xml @@ -0,0 +1,17 @@ + + + + +37.416384 + +-122.024853 +
701 FIRST AVE
+SUNNYVALE +CA + +94089-1019 +US +
+
diff --git a/testo.d/yahoo.test b/testo.d/yahoo.test new file mode 100644 index 000000000..a5e8bdc4d --- /dev/null +++ b/testo.d/yahoo.test @@ -0,0 +1,4 @@ + +gpsbabel -i yahoo -f ${REFERENCE}/yahoo.xml -o unicsv -F ${TMPDIR}/yahoo.csv +compare ${REFERENCE}/yahoo.csv ${TMPDIR}/yahoo.csv + diff --git a/xmlgeneric.cc b/xmlgeneric.cc index 15c301959..83bc27cdc 100644 --- a/xmlgeneric.cc +++ b/xmlgeneric.cc @@ -23,6 +23,7 @@ #include // for QHash #include // for QIODevice, QIODevice::ReadOnly #include // for QLatin1Char +#include #include // for QStringRef #include // for QTextCodec #include // for QXmlStreamAttributes @@ -44,7 +45,8 @@ enum xg_shortcut { xg_shortcut_ignore }; -static xg_tag_mapping* xg_tag_tbl; +static QList* xg_tag_tbl; +static bool dynamic_tag_tbl; static QHash* xg_shortcut_taglist; static QString rd_fname; @@ -65,25 +67,25 @@ static QTextCodec* codec = utf8_codec; // Qt has no vanilla ASCII encoding =( * xml strains and insulates us from a lot of the grubbiness of expat. */ -xg_callback* +XgCallbackBase* xml_tbl_lookup(const QString& tag, xg_cb_type cb_type) { const QByteArray key = tag.toUtf8(); const char* keyptr = key.constData(); - for (xg_tag_mapping* tm = xg_tag_tbl; tm->tag_cb != nullptr; ++tm) { - if ((cb_type == tm->cb_type) && str_match(keyptr, tm->tag_name)) { - return tm->tag_cb; + for (const auto& tm : qAsConst(*xg_tag_tbl)) { + if ((cb_type == tm.cb_type) && str_match(keyptr, tm.tag_name)) { + return tm.tag_cb; } } return nullptr; } void -xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding, +xml_common_init(const QString& fname, const char* encoding, const char** ignorelist, const char** skiplist) { rd_fname = fname; - xg_tag_tbl = tbl; + xg_encoding = encoding; if (encoding) { QTextCodec* tcodec = QTextCodec::codecForName(encoding); @@ -91,6 +93,7 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding, codec = tcodec; } } + xg_shortcut_taglist = new QHash; if (ignorelist != nullptr) { for (; ignorelist && *ignorelist; ++ignorelist) { @@ -104,14 +107,47 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding, } } +void +xml_init(const QString& fname, QList* tbl, const char* encoding, + const char** ignorelist, const char** skiplist, bool dynamic_tbl) +{ + xg_tag_tbl = tbl; + dynamic_tag_tbl = dynamic_tbl; + + xml_common_init(fname, encoding, ignorelist, skiplist); +} + +void +xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding, + const char** ignorelist, const char** skiplist) +{ + xg_tag_tbl = new QList; + dynamic_tag_tbl = true; + for (xg_tag_mapping* tm = tbl; tm->tag_cb != nullptr; ++tm) { + auto* cb = new XgFunctionPtrCallback(tm->tag_cb); + xg_tag_tbl->append({cb, tm->cb_type, tm->tag_name}); + } + + xml_common_init(fname, encoding, ignorelist, skiplist); +} + void xml_deinit() { + if (dynamic_tag_tbl) { + for (const auto& tm : qAsConst(*xg_tag_tbl)) { + delete tm.tag_cb; + } + delete xg_tag_tbl; + } + xg_tag_tbl = nullptr; + reader_data.clear(); rd_fname.clear(); - xg_tag_tbl = nullptr; + xg_encoding = nullptr; codec = utf8_codec; + delete xg_shortcut_taglist; xg_shortcut_taglist = nullptr; } @@ -129,7 +165,7 @@ xml_shortcut(const QStringRef& name) static void xml_run_parser(QXmlStreamReader& reader) { - xg_callback* cb; + XgCallbackBase* cb; QString current_tag; while (!reader.atEnd()) { @@ -163,7 +199,7 @@ xml_run_parser(QXmlStreamReader& reader) cb = xml_tbl_lookup(current_tag, cb_start); if (cb) { const QXmlStreamAttributes attrs = reader.attributes(); - cb(nullptr, &attrs); + (*cb)(nullptr, &attrs); } cb = xml_tbl_lookup(current_tag, cb_cdata); @@ -173,7 +209,7 @@ xml_run_parser(QXmlStreamReader& reader) // thus we will not process the EndElement case as we will issue a readNext first. // does a caller ever expect to be able to use both a cb_cdata and a // cb_end callback? - cb(c, nullptr); + (*cb)(c, nullptr); current_tag.chop(reader.qualifiedName().length() + 1); } break; @@ -185,7 +221,7 @@ xml_run_parser(QXmlStreamReader& reader) cb = xml_tbl_lookup(current_tag, cb_end); if (cb) { - cb(reader.name().toString(), nullptr); + (*cb)(reader.name().toString(), nullptr); } current_tag.chop(reader.qualifiedName().length() + 1); break; diff --git a/xmlgeneric.h b/xmlgeneric.h index c1e97fc82..c4e14dfe2 100644 --- a/xmlgeneric.h +++ b/xmlgeneric.h @@ -31,23 +31,126 @@ // be convenient to overload some day. using xg_string = const QString&; - enum xg_cb_type { cb_start = 1, cb_cdata, cb_end, }; -using xg_callback = void (xg_string, const QXmlStreamAttributes*); +class XgCallbackBase +{ +public: + XgCallbackBase() = default; + virtual ~XgCallbackBase() = default; + XgCallbackBase(const XgCallbackBase&) = delete; + XgCallbackBase& operator=(const XgCallbackBase&) = delete; + XgCallbackBase(XgCallbackBase&&) = delete; + XgCallbackBase& operator=(XgCallbackBase&&) = delete; + + virtual void operator()(xg_string string, const QXmlStreamAttributes* attrs) const = 0; +}; + +template +class XgFunctor : public XgCallbackBase +{ +public: + using XgCb = void (XgFormat::*)(xg_string, const QXmlStreamAttributes*); + XgFunctor(XgFormat* obj, XgCb cb) : that_(obj), cb_(cb) {} + void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override + { + (that_->*cb_)(string, attrs); + } +private: + XgFormat* that_; + XgCb cb_; +}; + +class XgFunctionPtrCallback : public XgCallbackBase +{ +public: + using XgCb = void (xg_string, const QXmlStreamAttributes*); + explicit XgFunctionPtrCallback(XgCb cb) : cb_(cb) {} + void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override + { + (*cb_)(string, attrs); + } + +private: + XgCb* cb_; +}; + +// xml processing uses a QList. +// You may generated this yourself. See method 1 below. +// Or it may be generated for you using one of the subsequent +// methods. +struct xg_tag_map_entry { + XgCallbackBase* tag_cb; + xg_cb_type cb_type; + const char* tag_name; +}; + +// Table generation from an array containing function pointers. +// The above table can be generated by xml_init. See method 2 below. +// This is how things done historically before the Format class was +// introduced. +using xg_callback = void (xg_string, const QXmlStreamAttributes*); struct xg_tag_mapping { xg_callback* tag_cb; xg_cb_type cb_type; const char* tag_name; }; -extern const char* xhtml_entities; +// Table generation from a list containing member function pointers. +// The above table can be generated by xml_init. See method 3 below. +template +struct xg_functor_map_entry { + using XgCb = void (MyFormat::*)(xg_string, const QXmlStreamAttributes*); + XgCb tag_cb; + xg_cb_type cb_type; + const char* tag_name; +}; + +template +QList* build_xg_tag_map(MyFormat* instance, const QList& map) +{ + auto* tag_tbl = new QList; + for (const auto& entry : qAsConst(map)) { + auto* tag_cb = new XgFunctor(instance, entry.tag_cb); + tag_tbl->append({tag_cb, entry.cb_type, entry.tag_name}); + } + return tag_tbl; +} +/* + * There are multiple ways to initialize with xml_init. + * + * 1. Build your own QList, and pass it. + * You own the table, you must do any required clean up. + * Your callbacks may be a mix of function pointers wrapped in XgFunctors + * and non-static member functions wrapped in XgFunctionPtrCallbacks. + * and XgFunctionPtrCallback(for static member functions or global functions) entries. + * xml_init(fname, tbl, encoding, ignorelist, skiplist, false); + * You must set the dynamic_tbl parameter to false so xml_deninit doesn't + * attempt to free the table resources when xml_deinit is called. + * + * 2. Have xml_init build and own a table of XgFunctionPtrCallback entries + * from an array of function pointers, i.e. a xg_tag_mapping array. + * This only works when all callbacks are function pointers. + * xml_init(fname, tbl, encoding, ignorelist, skiplist); + * Generated table entries will automatically be freed. + * + * 3. Have xml_init build and own a table of XgFunctor entries from a list + * of non-static member functions, i.e. a QList. + * This only works when all callbacks are non-static member functions. + * xml_init(fname, build_xg_tag_map(instance, map), encoding, ignorelist, skiplist, true); + * You must set the dynamic_tbl parameter to true to free the generated table + * resources when xml_deinit is called. + * + */ +void xml_init(const QString& fname, QList* tbl, const char* encoding, + const char** ignorelist = nullptr, + const char** skiplist = nullptr, bool dynamic_tbl = false); void xml_init(const QString& fname, xg_tag_mapping* tbl,const char* encoding, const char** ignorelist = nullptr, const char** skiplist = nullptr); diff --git a/yahoo.cc b/yahoo.cc index 72d312621..21c520056 100644 --- a/yahoo.cc +++ b/yahoo.cc @@ -20,36 +20,19 @@ */ +#include // for QXmlStreamAttributes + #include "defs.h" #include "yahoo.h" -#include "xmlgeneric.h" -#include +#include "xmlgeneric.h" // for xg_string, build_xg_tag_map, xml_deinit, xml_init, xml_read #define MYNAME "yahoo" -// static xg_callback wpt_s, wpt_lat, wpt_lon, wpt_e; -// static xg_callback wpt_addr /*, wpt_city, wpt_state, wpt_zip, wpt_country*/; -#if 0 -static xg_tag_mapping gl_map[] = { - { wpt_s, cb_start, "/ResultSet/Result" }, - { wpt_lat, cb_cdata, "/ResultSet/Result/Latitude" }, - { wpt_lon, cb_cdata, "/ResultSet/Result/Longitude" }, - { wpt_addr, cb_cdata, "/ResultSet/Result/Address" }, - { wpt_addr, cb_cdata, "/ResultSet/Result/City" }, - { wpt_addr, cb_cdata, "/ResultSet/Result/State" }, - { wpt_addr, cb_cdata, "/ResultSet/Result/Zip" }, - { wpt_addr, cb_cdata, "/ResultSet/Result/Country" }, - { wpt_e, cb_end, "/ResultSet/Result" }, - { nullptr, (xg_cb_type)0, nullptr} -}; -#endif - void YahooFormat::rd_init(const QString& fname) { -abort(); -// xml_init(fname, gl_map, nullptr); + xml_init(fname, build_xg_tag_map(this, gl_map), nullptr, nullptr, nullptr, true); } void @@ -97,20 +80,3 @@ YahooFormat::wpt_addr(xg_string args, const QXmlStreamAttributes*) } wpt_tmp->notes += args; } -#if 0 -ff_vecs_t yahoo_vecs = { - ff_type_file, - { ff_cap_read, ff_cap_none, ff_cap_none }, - yahoo_rd_init, - nullptr, - yahoo_rd_deinit, - nullptr, - yahoo_read, - nullptr, - nullptr, - &yahoo_args, - CET_CHARSET_ASCII, 0 /* CET-REVIEW */ - , NULL_POS_OPS, - nullptr -}; -#endif diff --git a/yahoo.h b/yahoo.h index 1dc7fead0..16d846afb 100644 --- a/yahoo.h +++ b/yahoo.h @@ -21,13 +21,14 @@ #ifndef YAHOO_H_INCLUDED_ #define YAHOO_H_INCLUDED_ +#include // for QList #include // for QString -#include // for QStringList -#include +#include // for QVector +#include // for QXmlStreamAttributes #include "defs.h" #include "format.h" // for Format -#include "xmlgeneric.h" // for Format +#include "xmlgeneric.h" // for xg_tag_map_entry, cb_cdata, XgFunctor, cb_end, cb_start class YahooFormat : public Format { @@ -42,7 +43,8 @@ public: return ff_type_file; } - QVector get_cap() const override { + QVector get_cap() const override + { return { ff_cap_read, // waypoints ff_cap_none, // tracks @@ -50,11 +52,13 @@ public: }; } - QString get_encode() const override { + QString get_encode() const override + { return CET_CHARSET_ASCII; } - int get_fixed_encode() const override { + int get_fixed_encode() const override + { return 0; } @@ -63,24 +67,34 @@ public: void rd_deinit() override; private: - Waypoint* wpt_tmp; - char* as; - - QVector yahoo_args = { - { - "addrsep", &as, - "String to separate concatenated address fields (default=\", \")", - ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr - }, - }; - void wpt_s(const QString &, const QXmlStreamAttributes *); - - void wpt_e(const QString &, const QXmlStreamAttributes *); - void wpt_lat(const QString &, const QXmlStreamAttributes *); - void wpt_lon(const QString &, const QXmlStreamAttributes *); - void wpt_addr(const QString &, const QXmlStreamAttributes *); -// xg_tag_mapping gl_map[]; - + Waypoint* wpt_tmp; + char* as; + + QVector yahoo_args = { + { + "addrsep", &as, + "String to separate concatenated address fields (default=\", \")", + ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr + }, + }; + + void wpt_s(const QString&, const QXmlStreamAttributes*); + void wpt_e(const QString&, const QXmlStreamAttributes*); + void wpt_lat(const QString&, const QXmlStreamAttributes*); + void wpt_lon(const QString&, const QXmlStreamAttributes*); + void wpt_addr(const QString&, const QXmlStreamAttributes*); + + QList> gl_map = { + {&YahooFormat::wpt_s, cb_start, "/ResultSet/Result"}, + {&YahooFormat::wpt_lat, cb_cdata, "/ResultSet/Result/Latitude"}, + {&YahooFormat::wpt_lon, cb_cdata, "/ResultSet/Result/Longitude"}, + {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Address"}, + {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/City"}, + {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/State"}, + {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Zip"}, + {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Country"}, + {&YahooFormat::wpt_e, cb_end, "/ResultSet/Result"} + }; }; #endif // YAHOO_H_INCLUDED_ -- 2.30.2